Skip to content

fix(relinker): accept the input's directory as a module directory - #3850

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
SandstromPL:fix/relinker-module-dir-root
Oct 12, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
SandstromPL:fix/relinker-module-dir-root

Conversation

@SandstromPL

Copy link
Copy Markdown
Contributor

What

--module-dir . did not work for modules kept next to the executable, the layout Hades (PPSA03355) uses for libfmod.prx and libfmodstudio.prx, which it loads with dlopen("/app0/libfmod.prx") (#2724). Three problems:

  • The scan of a module directory read the input executable as a guest module and failed (ELF is not a shared module, reported in Hades (PPSA03355) on Windows: start-up faults in the title's own libc.prx before any file is opened #2724). The input executable is now skipped when scanning module directories.
  • Modules in the module parent directory got app0/./<name>.guest.prx as output, runtime and Linux DT_NEEDED path. They now get app0/<name>.guest.prx, which is where dlopen looks (<requested path>.guest.prx, DynamicLoader.cpp:131). One helper builds the path for both uses.
  • (root / ".").lexically_normal() keeps a trailing separator, so the directory did not match the module's parent and the module was not treated as runtime-loaded (it went into Linux DT_NEEDED and was started without start arguments). fix(relinker): normalize additional module directory paths #3509 fixes that normalization; this branch carries its commit unchanged.

Non-ELF files are still skipped, and the scan stays non-recursive, so sce_sys/ and dump folders are not walked (#2708). USAGE.md documents ..

Tested

Ubuntu 24.04, GCC 13.3.0, relinker-only build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release): 114/114 tests pass.

  • guest_module_dir gains two cases, run for both Linux and Windows output:
    • a root-level runtime module with --module-dir .: it is written to app0/root.prx.guest.prx, stays out of DT_NEEDED on Linux, and exports __aps5_guest_initialize on Windows;
    • a root-level module the executable needs: Linux DT_NEEDED is $ORIGIN/app0/needed.prx.guest.prx.
  • Both new cases fail on fix(relinker): normalize additional module directory paths #3509 alone, without this commit. The scan stops on the input (input.elf: Invalid string or symbol table), and with only the scan fix the path is $ORIGIN/app0/./root.prx.guest.prx.
  • Ran the reporter's command shape by hand with the test fixtures, relinker --windows --module-dir . game/eboot.elf app.exe with libfmod.prx next to eboot.elf: it writes game/app0/libfmod.prx.guest.prx. A second Linux run with the output .elf inside the module directory stops with the existing "Executable output would overwrite an input module" error. The output is deliberately not skipped by the scan, since skipping it would bypass that guard when the output path names a real module. An earlier relinked output left in a module directory is scanned like any other ELF there (true of every --module-dir), so the output belongs outside the game directory.
  • tools/check_conventions.py from main passes on this branch with this title and body.

Not tested: Windows host (the .exe run in the test only runs on Windows) or the title itself. I don't have a Hades dump. The reporter's workaround in #2724 (copying the .guest.prx files to app0/) is the layout this change produces.

Checklist

What

--module-dir . did not work for modules kept next to the executable, the layout Hades (PPSA03355) uses for libfmod.prx and libfmodstudio.prx, which it loads with dlopen("/app0/libfmod.prx") (#2724). Three problems:

  • The scan of a module directory read the input executable as a guest module and failed (ELF is not a shared module, reported in Hades (PPSA03355) on Windows: start-up faults in the title's own libc.prx before any file is opened #2724). The input executable is now skipped when scanning module directories.
  • Modules in the module parent directory got app0/./<name>.guest.prx as output, runtime and Linux DT_NEEDED path. They now get app0/<name>.guest.prx, which is where dlopen looks (<requested path>.guest.prx, DynamicLoader.cpp:131). One helper builds the path for both uses.
  • (root / ".").lexically_normal() keeps a trailing separator, so the directory did not match the module's parent and the module was not treated as runtime-loaded (it went into Linux DT_NEEDED and was started without start arguments). fix(relinker): normalize additional module directory paths #3509 fixes that normalization; this branch carries its commit unchanged.

Non-ELF files are still skipped, and the scan stays non-recursive, so sce_sys/ and dump folders are not walked (#2708). USAGE.md documents ..

Tested

Ubuntu 24.04, GCC 13.3.0, relinker-only build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release): 114/114 tests pass.

  • guest_module_dir gains two cases, run for both Linux and Windows output:
    • a root-level runtime module with --module-dir .: it is written to app0/root.prx.guest.prx, stays out of DT_NEEDED on Linux, and exports __aps5_guest_initialize on Windows;
    • a root-level module the executable needs: Linux DT_NEEDED is $ORIGIN/app0/needed.prx.guest.prx.
  • Both new cases fail on fix(relinker): normalize additional module directory paths #3509 alone, without this commit. The scan stops on the input (input.elf: Invalid string or symbol table), and with only the scan fix the path is $ORIGIN/app0/./root.prx.guest.prx.
  • Ran the reporter's command shape by hand with the test fixtures, relinker --windows --module-dir . game/eboot.elf app.exe with libfmod.prx next to eboot.elf: it writes game/app0/libfmod.prx.guest.prx. A second Linux run with the output .elf inside the module directory stops with the existing "Executable output would overwrite an input module" error. The output is deliberately not skipped by the scan, since skipping it would bypass that guard when the output path names a real module. An earlier relinked output left in a module directory is scanned like any other ELF there (true of every --module-dir), so the output belongs outside the game directory.
  • tools/check_conventions.py from main passes on this branch with this title and body.

Not tested: Windows host (the .exe run in the test only runs on Windows) or the title itself. I don't have a Hades dump. The reporter's workaround in #2724 (copying the .guest.prx files to app0/) is the layout this change produces.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: fix(relinker): normalize additional module directory paths #3509 (its commit is the first one on this branch; review only the second)
  • AI-assisted: yes (Claude Code); I reviewed and tested every line

Fixes #2724

@xXPBSXx

xXPBSXx commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Windows test of this PR with Hades (PPSA03355), as asked in #2724: Windows 10, MinGW/Ninja Release build of main c5da823 with this PR merged in (merge was clean), relinked from the reconstructed ELF with --windows --registry --module-dir . (the input directory holds eboot.elf, sce_module/ and the two root-level modules).

Not tested: Linux, and a run without . (the old workaround layout) for comparison.

AI-assisted (Claude Code).

@oneandonlydean
oneandonlydean merged commit 26d87ee into boykopovar:main Oct 12, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hades (PPSA03355) on Windows: start-up faults in the title's own libc.prx before any file is opened

4 participants